use crate::error::{ExpansionError, Result};
use crate::manifest::{ContributionKeywordDefinition, Manifest};
use crate::services::replenish::replenish_all;
use std::collections::{HashMap, HashSet};

/// Manages the loading, validation, and querying of manifests.
pub trait IManifestManager {
    fn get_manifest(&self, name: &str) -> Option<&Manifest>;
    fn get_should_activates(&self, event: &str) -> Vec<&Manifest>;
    fn get_should_deactivates(&self, event: &str) -> Vec<&Manifest>;
}

pub struct ManifestManager {
    manifests: HashMap<String, Manifest>,
    keywords: HashMap<String, ContributionKeywordDefinition>,
}

impl ManifestManager {
    pub fn new(manifests: Vec<Manifest>) -> Result<Self> {
        let mut manifest_map: HashMap<String, Manifest> = manifests.into_iter().map(|m| (m.name.clone(), m)).collect();
        replenish_all(&mut manifest_map);

        let keywords = discover_keywords(&manifest_map);
        validate_dependencies(&manifest_map)?;
        validate_contribution_targets(&manifest_map, &keywords)?;
        validate_contribution_uniqueness(&manifest_map, &keywords)?;
        validate_namespaces(&manifest_map, &keywords)?;

        Ok(Self { manifests: manifest_map, keywords })
    }
}

impl IManifestManager for ManifestManager {
    fn get_manifest(&self, name: &str) -> Option<&Manifest> {
        self.manifests.get(name)
    }

    fn get_should_activates(&self, event: &str) -> Vec<&Manifest> {
        self.manifests.values().filter(|m| m.activation_events.iter().any(|e| e.is_match(event))).collect()
    }

    fn get_should_deactivates(&self, event: &str) -> Vec<&Manifest> {
        self.manifests.values().filter(|m| m.deactivation_events.iter().any(|e| e.is_match(event))).collect()
    }
}

fn validate_dependencies(manifests: &HashMap<String, Manifest>) -> Result<()> {
    for manifest in manifests.values() {
        for dep_name in manifest.dependencies.keys() {
            if !manifests.contains_key(dep_name) {
                return Err(ExpansionError::ManifestValidation(format!(
                    "In manifest '{}', dependency '{}' is not found.",
                    manifest.name, dep_name
                )));
            }
        }
    }
    Ok(())
}

fn discover_keywords(manifests: &HashMap<String, Manifest>) -> HashMap<String, ContributionKeywordDefinition> {
    manifests.values().flat_map(|m| m.contribution_keywords.iter().map(|k| (k.name.clone(), k.clone()))).collect()
}

fn validate_contribution_targets(manifests: &HashMap<String, Manifest>, keywords: &HashMap<String, ContributionKeywordDefinition>) -> Result<()> {
    for manifest in manifests.values() {
        if let Some(contributes) = &manifest.contributes {
            for keyword_name in contributes.keys() {
                if !keywords.contains_key(keyword_name) {
                    return Err(ExpansionError::ManifestValidation(format!(
                        "In manifest '{}', contribute target '{}' is not a registered keyword.",
                        manifest.name, keyword_name
                    )));
                }
            }
        }
    }
    Ok(())
}

fn validate_contribution_uniqueness(manifests: &HashMap<String, Manifest>, _keywords: &HashMap<String, ContributionKeywordDefinition>) -> Result<()> {
    let mut all_contribute_names: HashMap<&str, HashSet<&str>> = HashMap::new();
    let mut all_point_names: HashMap<&str, HashSet<&str>> = HashMap::new();

    for manifest in manifests.values() {
        if let Some(contributes) = &manifest.contributes {
            for (keyword, defs) in contributes {
                let name_set = all_contribute_names.entry(keyword).or_default();
                for def in defs {
                    if !name_set.insert(&def.name) {
                        return Err(ExpansionError::ManifestValidation(format!(
                            "Duplicate contribute name '{}' found for keyword '{}'.",
                            def.name, keyword
                        )));
                    }
                }
            }
        }
        if let Some(points) = &manifest.contribution_points {
            for (keyword, defs) in points {
                let name_set = all_point_names.entry(keyword).or_default();
                for def in defs {
                    if !name_set.insert(&def.name) {
                        return Err(ExpansionError::ManifestValidation(format!(
                            "Duplicate contribution_point name '{}' found for keyword '{}'.",
                            def.name, keyword
                        )));
                    }
                }
            }
        }
    }
    Ok(())
}

fn validate_namespaces(manifests: &HashMap<String, Manifest>, keywords: &HashMap<String, ContributionKeywordDefinition>) -> Result<()> {
    for manifest in manifests.values() {
        let check = |name: &str, keyword: &str| -> Result<()> {
            if keywords.get(keyword).map_or(false, |k| k.disable_name_obey_namespace_rule) {
                return Ok(());
            }
            if !name.starts_with(&manifest.name) {
                return Err(ExpansionError::ManifestValidation(format!(
                    "In manifest '{}', name '{}' for keyword '{}' does not follow namespace rule.",
                    manifest.name, name, keyword
                )));
            }
            Ok(())
        };

        if let Some(contributes) = &manifest.contributes {
            for (keyword, defs) in contributes {
                for def in defs { check(&def.name, keyword)?; }
            }
        }
        if let Some(points) = &manifest.contribution_points {
            for (keyword, defs) in points {
                for def in defs { check(&def.name, keyword)?; }
            }
        }
    }
    Ok(())
}
